YUI().use("console", "yuitest", function(Y) {
  /**************************************************************************
   * Test Helpers
   *************************************************************************/

  // Y.ObjectAssert.areEqual won't catch extra properties that are in the
  // actual but not expected object.  This function makes sure that the actual
  // and expected objects have the exact same properties and that they are
  // equal.  Also, do an equivalence (===) test, not equal (==).
  Y.ObjectAssert.areExactlySame = function(expected, actual, message) {
    Y.Object.each(expected, function(value, name){
      Y.Assert.areSame(expected[name], actual[name],
          Y.Assert._formatMessage(message,
              "Values should be equal for property " + name));
    });
    Y.Object.each(actual, function(value, name) {
      Y.ObjectAssert.has(name, expected,
          Y.Assert._formatMessage(message,
              "Unexpected property " + name));
    });
  };

  // There are a number of for/in loops in the traits implementation.  Adding
  // a few properties to the root Object here to make sure they all check
  // hasOwnProperty when appropriate.
  Object.prototype.quux =
    Object.prototype.f =
    Object.prototype.foo = "obj-proto";


  /**************************************************************************
   * Class Tests
   * - Note that the class implementation in JSTraits is secondary to the
   *   core traits functionality (and should be able to be replaced with any
   *   other similar classical OOP emulation package) so we're only going to
   *   do a few rudimentary tests, nothing exhaustive.
   *************************************************************************/
  var class_suite = new Y.Test.Suite("Class Suite");

  class_suite.add(new Y.Test.Case({
    name: "Class Definition",

    testPublicMembers: function() {
      var Q = Class.define({ members: {
        num: 5,
        fun: function() { return this.num }
      }});

      var q = new Q();
      Y.Assert.areEqual(5, q.num);
      Y.Assert.areEqual(q.num, q.fun());
      Y.Assert.isString(q.toString());
      Y.Assert.isInstanceOf(Q, q);
      Y.Assert.isInstanceOf(Object, q);
      Y.Assert.isInstanceOf(Class, q);

      q.num = 3;
      var q2 = new Q();
      Y.Assert.areEqual(5, q2.num);
      Y.Assert.areEqual(3, q.num);
    },

    testConstructor: function() {
      var extern = false;
      var Q = Class.define({ members: {
        init: function(num) {
          extern = true;
          this.num = num;
        },
        num: 5,
        fun: function() { return this.num }
      }});

      var P = Class.define({ superclass: Q });
      Y.Assert.isFalse(extern,
          "Subclassing should not invoke superclass's init method");

      var q = new Q(7);
      Y.Assert.areEqual(7, q.fun());
    }
  }));

  class_suite.add(new Y.Test.Case({
    name: "Subclass",

    setUp: function() {
      this.Q1 = Class.define({
        members: {
          num: 1,
          q1: true,
          fun: function() { return 1; }
        }
      });
      this.Q2 = Class.define({
        superclass: this.Q1,
        members: {
          num: 2,
          q2: true,
          overrideme: 10,
          fun: function(o) { if (o) return this._super(); else return 2; }
        }
      });
      this.Q3 = Class.define({
        superclass: this.Q2,
        members: {
          num: 3,
          q3: true,
          overrideme: 5,
          fun: function(o) { if (o) return this._super(true); else return 3; }

        }
      });
    },

    testSubclass: function() {
      var q1 = new this.Q1(),
          q2 = new this.Q2(),
          q3 = new this.Q3();

      Y.Assert.areEqual(1, q1.num);
      Y.Assert.areEqual(2, q2.num);
      Y.Assert.areEqual(3, q3.num);

      Y.Assert.isInstanceOf(this.Q1, q1);
      Y.Assert.isInstanceOf(this.Q1, q2);
      Y.Assert.isInstanceOf(this.Q2, q2);
      Y.Assert.isInstanceOf(this.Q1, q3);
      Y.Assert.isInstanceOf(this.Q2, q3);
      Y.Assert.isInstanceOf(this.Q3, q3);

      Y.Assert.isTrue(q1.q1);
      Y.Assert.isTrue(q2.q1);
      Y.Assert.isTrue(q2.q2);
      Y.Assert.isTrue(q3.q1);
      Y.Assert.isTrue(q3.q2);
      Y.Assert.isTrue(q3.q3);

      Y.Assert.areEqual(10, q2.overrideme);
      Y.Assert.areEqual(5, q3.overrideme);
    },

    testSuper: function() {
      var q1 = new this.Q1(),
          q2 = new this.Q2(),
          q3 = new this.Q3();

      Y.Assert.areEqual(1, q1.fun());
      Y.Assert.areEqual(2, q2.fun());
      Y.Assert.areEqual(1, q2.fun(true));
      Y.Assert.areEqual(3, q3.fun());
      Y.Assert.areEqual(1, q3.fun(true));
    }
  }));


  /**************************************************************************
   * Trait Definition Tests
   * - Test the core features of JSTraits: trait definition and using traits
   *   in classes.
   *************************************************************************/
  var trait_suite = new Y.Test.Suite("Trait Suite");

  trait_suite.add(new Y.Test.Case({
    name: "Trait Definition",

    testTraitInClass: function() {
      var TSimple = Trait.define({
        methods: {
          getName: function() { return "TSimple"; }
        }
      });
      var BasicTrait = Class.define({ uses: TSimple });
      var x = new BasicTrait();

      Y.Assert.isFunction(x.getName);
      Y.Assert.areEqual("TSimple", x.getName());
      Y.Assert.isInstanceOf(Trait, TSimple);
      Y.Assert.isInstanceOf(Class, x);

      Y.Assert.isTrue(BasicTrait.does(TSimple));
      Y.ArrayAssert.itemsAreSame([], TSimple.subtraits());
      Y.ObjectAssert.areExactlySame({}, TSimple.requires());
      Y.ObjectAssert.areExactlySame({ getName: (new BasicTrait()).getName },
                              TSimple.methods());
    },

    testRequires: function() {
      var TNoRequire = Trait.define({}),
          TOneRequire = Trait.define({ requires: 'foo' }),
          TMultiRequire = Trait.define({ requires: ['bar', 'baaz'] }),
          TInheritRequire = Trait.define({ uses: TMultiRequire }),
          TInheritComposeRequire = Trait.define({
            uses: [TMultiRequire, TOneRequire, TNoRequire],
            requires: ['quux', 'foo']
          }),
          TInheritComposeRequire2 = Trait.define({
            uses: TInheritComposeRequire
          });

      // Test that requires arguments are interpreted correctly
      Y.ObjectAssert.areExactlySame({}, TNoRequire.requires());
      Y.ObjectAssert.areExactlySame({'foo': true}, TOneRequire.requires());
      Y.ObjectAssert.areExactlySame({'bar': true, 'baaz': true},
          TMultiRequire.requires());
      Y.ObjectAssert.areExactlySame({'bar': true, 'baaz': true},
          TInheritRequire.requires());
      Y.ObjectAssert.areExactlySame(
        {'bar': true, 'baaz': true, 'foo': true, 'quux': true},
        TInheritComposeRequire.requires()
      );
      Y.ObjectAssert.areExactlySame(
        {'bar': true, 'baaz': true, 'foo': true, 'quux': true},
        TInheritComposeRequire2.requires()
      );

      // Requires values and methods keys must be disjoint.
      try {
        Trait.define({ requires: 'foo', methods: { foo: function() {} } });
        Y.Assert.fail("define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention errant method name");
      }

      // Requirements can be fullfilled by a composed trait, trait method,
      // class method, or superclass+ method...
      var TProvidesFoo = Trait.define({ methods: { foo: function(){} } }),
          TParentProvidesFoo = Trait.define({ uses: TProvidesFoo }),
          TFulfilledByComosedTrait = Trait.define({
            uses: [TProvidesFoo, TOneRequire]
          }),
          TFulfilledByComposedTrait2 = Trait.define({
            uses: [TParentProvidesFoo, TOneRequire]
          }),
          TFulfilledByTraitMethod = Trait.define({
            uses: [TOneRequire],
            methods: { foo: function(){} }
          }),
          CFullfilledByClassMethod = Class.define({
            uses: TOneRequire,
            members: { foo: function(){} }
          }),
          CProvidesFoo = Class.define({
            members: { foo: function(){} }
          }),
          CFullfilledBySuperclassMethod = Class.define({
            superclass: CProvidesFoo,
            uses: TOneRequire
          });

      Y.Assert.isFalse(!!TFulfilledByComosedTrait.requires('foo'));
      Y.Assert.isFalse(!!TFulfilledByComposedTrait2.requires('foo'));
      Y.Assert.isFalse(!!TFulfilledByTraitMethod.requires('foo'));

      // All trait requirements must be fullfilled if added to a class
      try {
        Class.define({
          uses: TOneRequire
        });
        Y.Assert.fail("class define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention missing method name");
      }

      try {
        Class.define({
          uses: TInheritComposeRequire2,
          members: {
            bar: function() {},
            baaz: function() {},
            foo: function() {}
          }
        });
        Y.Assert.fail("class define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("quux") >= 0,
            "error message should mention missing method name");
      }

      try {
        Class.define({
          uses: TOneRequire,
          members: { foo: 23 } // required provider must be a function
        });
        Y.Assert.fail("class define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention missing method name");
      }
    },

    testExcludes: function() {
      var f1 = function(){},
          f2 = function(){},
          f3 = function(){},
          g1 = function(){},
          g2 = function(){},
          g3 = function(){};

      // excludes accepts a string if only one method is being excluded
      var Tf1 = Trait.define({methods:{f:f1}}),
          Tf2 = Trait.define({
            uses:Tf1.excludes('f'),
            methods:{f:f2}
          });
      Y.Assert.areSame(f2, Tf2.methods('f'));

      // otherwise excludes accepts a string array of method names
      var T1 = Trait.define({methods:{f:f1, g:g1}}),
          T2 = Trait.define({
            uses: T1.excludes(['f', 'g']),
            methods: {f: f2, g: g2 }
          });
      Y.Assert.areSame(f2, T2.methods('f'));
      Y.Assert.areSame(g2, T2.methods('g'));

      // should be able to chain calls to excludes
      T2 = Trait.define({
        uses: T1.excludes('f').excludes('g'),
        methods: {f: f2, g: g2 }
      });
      Y.Assert.areSame(f2, T2.methods('f'));
      Y.Assert.areSame(g2, T2.methods('g'));

      // can exclude methods from parent traits
      var TNone = Trait.define({uses: Tf1});
      Tf2 = Trait.define({
        uses:TNone.excludes('f'),
        methods:{f:f2}
      });
      Y.Assert.areSame(f2, Tf2.methods('f'));

      // can't exclude non-provided methods
      try {
        var TError = Trait.define({
          uses: Tf1.excludes('bogus'),
          methods: { bogus: function(){} }
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("bogus") >= 0,
            "error message should mention missing method name");
      }

      // Not sure you why you'd want to, but should be able to exclude the same
      // method more than once
      Tf1 = Trait.define({methods:{f:f1}}),
      Tf2 = Trait.define({
        uses:Tf1.excludes('f').excludes('f'),
        methods:{f:f2}
      });
      Y.Assert.areSame(f2, Tf2.methods('f'));

      // Excluded methods must be provided by ...
      // 1) another composed trait
      Tf1 = Trait.define({methods:{f:f1}});
      Tf2 = Trait.define({methods:{f:f2}});
      Trait.define({
        uses: [Tf1.excludes('f'), TNone]
      });
      // 2) the parent trait (already checked above)
      // 3) class method
      var c1 = Class.define({
        uses: Tf1.excludes('f'),
        members: {'f': function(){}}
      });
      // 4) superclass method
      var c2 = Class.define({ superclass: c1, uses: Tf2.excludes('f') });

      try {
        var CError = Class.define({
          uses: Tf1.excludes('f')
        });
        Y.Assert.fail("class define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("f") >= 0,
            "error message should mention missing method name");
      }
    },

    testAliases: function() {
      var f1 = function(){},
          f2 = function(){},
          f3 = function(){},
          g1 = function(){},
          g2 = function(){},
          g3 = function(){};

      // aliases accepts a hash of aliases to implemented method names
      var Tf1 = Trait.define({
        methods: {f: f1}
      });
      var Tf2 = Trait.define({
        uses: Tf1.aliases({foo: 'f', bar: 'f'}),
        methods: {f:f2}
      });
      Y.Assert.areSame(f2, Tf2.methods('f'));
      Y.Assert.areSame(f1, Tf2.methods('foo'));
      Y.Assert.areSame(f1, Tf2.methods('bar'));

      // should be able to chain calls to aliases
      var T1 = Trait.define({
        methods: {f: f1, g: g1}
      });
      var T2 = Trait.define({
        uses: T1.
          aliases({foo: 'f'}).excludes('f').excludes('g').aliases({bar: 'g'}),
        methods: {f: f2, g: g2 }
      });
      Y.Assert.areSame(f2, T2.methods('f'));
      Y.Assert.areSame(g2, T2.methods('g'));
      Y.Assert.areSame(f1, T2.methods('foo'));
      Y.Assert.areSame(g1, T2.methods('bar'));

      // can alias methods from parent traits
      var TNone = Trait.define({uses: Tf1});
      Tf2 = Trait.define({
        uses:TNone.aliases({foo: 'f'})
      });
      Y.Assert.areSame(f1, Tf2.methods('foo'));
      Y.Assert.areSame(f1, Tf2.methods('f'));

      // can't alias non-existent methods
      Tf1 = Trait.define({
        methods: {f: f1}
      });
      try {
        var TError = Trait.define({
          uses: Tf1.aliases({ foo: 'bogus' })
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("bogus") >= 0,
            "error message should mention missing method name");
        Y.Assert.isTrue(e.toString().indexOf("bogus") >= 0,
            "error message should mention alias target");
      }

      // method names and aliases must be disjoint
      Tf1 = Trait.define({
        methods: {f: f1, g: g1}
      });
      try {
        var TError = Trait.define({
          uses: Tf1.aliases({ f: 'g' }),
          methods: { foo: f3 }
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("f") >= 0,
            "error message should mention conflicting method name");
      }

      // alias names and required method names must be disjoint
      Tf1 = Trait.define({
        methods: {f: f1, g: g1},
        requires: 'foo'
      });
      try {
        var TError = Trait.define({
          uses: Tf1.aliases({ foo: 'f' }),
          requires: 'foo'
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention conflicting method name");
      }

      // Not sure you why you'd want to, but should be able to use the same
      // alias more than once, with later definitions beating earlier ones
      Tf1 = Trait.define({methods:{f:f1, g:g1}}),
      Tf2 = Trait.define({
        uses:Tf1.aliases({foo: 'f'}).aliases({foo: 'g'}),
        methods:{f:f2}
      });
      Y.Assert.areSame(g1, Tf2.methods('foo'));

      // Can assign multiple aliases to the same method
      Tf1 = Trait.define({methods:{f:f1}}),
      Tf2 = Trait.define({
        uses:Tf1.aliases({foo: 'f'}).aliases({bar: 'f'}),
        methods:{f:f2}
      });
      Y.Assert.areSame(f1, Tf2.methods('foo'));
      Y.Assert.areSame(f1, Tf2.methods('bar'));
      Y.Assert.areSame(f2, Tf2.methods('f'));

      // aliases can create conflicts
      Tf1 = Trait.define({
        methods: {foo: f1}
      });
      Tf2 = Trait.define({
        methods: {bar: g1}
      });
      try {
        var TError = Trait.define({
          uses: [Tf1.aliases({ foo: 'bar' }), Tf2]
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention conflicting method name");
      }
    },

    testConflicts: function() {
      var f1 = function(){},
          f2 = function(){},
          f3 = function(){},
          g1 = function(){},
          g2 = function(){},
          g3 = function(){};

      // no conflict if multiple subtraits provide the exact same method
      var T1 = Trait.define({
        methods: {f: f1}
      });
      var T2 = Trait.define({
        methods: {f: f1}
      });
      var T3 = Trait.define({
        uses:[T1, T2]
      });
      Y.Assert.areSame(f1, T3.methods('f'));

      // ... but there is a conflict if the methods aren't the same
      try {
        T1 = Trait.define({
          methods: {foo: f1}
        });
        T2 = Trait.define({
          methods: {foo: f2}
        });
        var TError = Trait.define({
          uses: [T1, T2]
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention conflicting method name");
      }

      // conflicts can come from anywhere up the trait chain
      try {
        var T3 = Trait.define({
          uses: T1
        });
        var T4 = Trait.define({
          uses: T2
        });
        TError = Trait.define({
          uses: [T3, T4]
        });
        Y.Assert.fail("trait define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention conflicting method name");
      }

      // conflicts are resolved by overriding
      var TGood = Trait.define({
        uses: [T3, T4],
        methods: { foo: g2 }
      });
      Y.Assert.areSame(g2, TGood.methods('foo'));

      // can only override trait method names with methods in a class def
      try {
        var CError = Class.define({
          uses: [T1, T2],
          members: { foo: 3 }
        });
        Y.Assert.fail("class define should have failed");
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention bad member name");
      }

      // conflicts can be resolved by
      // 1) excluding all but one of the definitions
      Trait.define({
        uses: [T1.excludes('foo'), T2]
      });
      // 2) overriding in the parent trait
      Trait.define({
        uses: [T1, T2],
        methods: { foo: g3 }
      });
      // 3) overriding in class method
      var c1 = Class.define({
        uses: [T1, T2],
        members: {foo: g3}
      });

      // but a superclass override shouldn't work
      var c1 = Class.define({
        members: {foo: g3}
      });
      try {
        var c2 = Class.define({ superclass: c1, uses: [T1, T2] });
      } catch(e) {
        Y.Assert.isInstanceOf(Trait.TraitError, e);
        Y.Assert.isTrue(e.toString().indexOf("foo") >= 0,
            "error message should mention bad member name");
      }
    }
  }));

  trait_suite.add(new Y.Test.Case({
    name: "Trait Use in Classes",

    testFlattening: function() {
      var Adder = Trait.define({
        requires: 'getNum',
        methods: {
          add: function(x) { return this.getNum() + x; }
        }
      });
      var Subtracter = Trait.define({
        requires: 'add',
        methods: {
          subtract: function(x) { return this.add(-1*x); }
        }
      });
      var NumClass = Class.define({
        uses: [Subtracter, Adder.aliases({doAdd: 'add'})],
        members: {
          init: function() { this.num = 42; },
          useAddInClass: function() { return this.add(1); },
          getNum: function() { return this.num; },
          dontoverrideme: function() { return "superclass"; }
        }
      });
      var n = new NumClass();

      // traits should have access to class's methods
      Y.Assert.areSame(52, n.add(10));
      // classes should have access to trait's methods
      Y.Assert.areSame(43, n.useAddInClass());
      // aliases should work
      Y.Assert.areSame(44, n.doAdd(2));
      // traits can use other traits' methods
      Y.Assert.areSame(40, n.subtract(2));

      var Doubler = Trait.define({
        methods: {
          getNum: function(x) { return this._super() * 2; },
          getNonMethod: function() { return this.aString; },
          overrideme: function() { return "trait"; },
          dontoverrideme: function() { return "trait2"; }
        }
      });
      var NumClassChild = Class.define({
        uses: Doubler,
        superclass: NumClass,
        members: {
          subtract: function(x) { return this._super(x*2); },
          aString: "foobar",
          overrideme: function() { return "class"; }
        }
      });
      var n2 = new NumClassChild();

      // traits can access supermethods (although it's not a good practice)
      Y.Assert.areSame(84, n2.getNum());
      // subclasses should inherit parent's trait methods
      Y.Assert.areSame(94, n2.add(10));
      // ... and be able to override them
      Y.Assert.areSame(80, n2.subtract(2));
      // we strongly disadvise it, but traits should be able to access
      // instance variables directly
      Y.Assert.areSame("foobar", n2.getNonMethod());

      // class methods override trait methods
      Y.Assert.areSame("class", n2.overrideme());
      // and trait methods override superclass methods
      Y.Assert.areSame("trait2", n2.dontoverrideme());
    }
  }));

  trait_suite.add(new Y.Test.Case({
    name: "Trait Reflection Methods",

    // note 'requires' and 'methods' were already used extensively in support
    // of previous tests, so we're not checking them here, although it wouldn't
    // hurt to do so...

    testDoes: function() {
      var T1 = Trait.define({});
      // a trait does itself
      Y.Assert.isTrue(T1.does(T1));
      // but not some other trait
      var T2 = Trait.define({});
      Y.Assert.isFalse(T1.does(T2));
      Y.Assert.isFalse(T2.does(T1));

      var T3 = Trait.define({ uses: [T1, T2] });
      // a trait does its subtraits
      Y.Assert.isTrue(T3.does(T1));
      Y.Assert.isTrue(T3.does(T2));
      // and itself
      Y.Assert.isTrue(T3.does(T3));

      var T4 = Trait.define({ uses: [T1, T3], methods: {f: function(){}} });
      // a trait does its subtraits' subrtaits...
      Y.Assert.isTrue(T4.does(T2));
      // and its subtraits
      Y.Assert.isTrue(T4.does(T3));
      Y.Assert.isTrue(T4.does(T1));

      var T5 = Trait.define({
        uses: T4.excludes('f'),
        methods: {f: function(){}}
      });
      // a trait does another trait even if all its methods were excluded
      Y.Assert.isTrue(T5.does(T4));

      // calling does with no argument should return an array of all
      // consumed traits
      Y.ArrayAssert.itemsAreSame([T1], T1.does());
      Y.ArrayAssert.itemsAreSame([T2], T2.does());
      Y.ArrayAssert.containsItems([T1, T2, T3], T3.does());
      Y.Assert.areSame(3, T3.does().length);
      Y.ArrayAssert.containsItems([T1, T2, T3, T4], T4.does());
      Y.Assert.areSame(4, T4.does().length);
      Y.ArrayAssert.containsItems([T1, T2, T3, T4, T5], T5.does());
      Y.Assert.areSame(5, T5.does().length);
      
      // A class does all used traits
      var C1 = Class.define({ uses: T1 });
      Y.Assert.isTrue(C1.does(T1));
      Y.Assert.isFalse(C1.does(T2));

      // a class does all used traits subtraits
      var C3 = Class.define({ uses: T3 });
      Y.Assert.isTrue(C3.does(T3));
      Y.Assert.isTrue(C3.does(T2));
      Y.Assert.isTrue(C3.does(T1));

      // calling does with no argument should return an array of all
      // consumed traits, including the implicit anonymous subtrait associated
      // with the class
      Y.ArrayAssert.containsItems([T1, T2, T3], C3.does());
      Y.Assert.areSame(3+1, C3.does().length);

      // a class with no traits does, uh, nothing
      var CNone = Class.define({});
      Y.Assert.isFalse(CNone.does(T1));
      Y.Assert.areSame(0, CNone.does().length);

      // a class does all inherited subraits
      var CA = Class.define({ uses: T1 }),
          CB = Class.define({ uses: T2, superclass: CA }),
          CC = Class.define({ superclass: CB });
      Y.ArrayAssert.containsItems([T1, T2], CC.does());
      Y.Assert.areSame(2+2, CC.does().length); // T1 + T2 + 2 anonymous traits

      // a class get traits from multiple sources it should only list them
      // once in does()
      var CD = Class.define({ uses: T2, superclass: CB });
      Y.ArrayAssert.containsItems([T1, T2], CD.does());
      Y.Assert.areSame(2+3, CD.does().length); // T1 + T2 + 3 anonymous traits

      // does should be defined as an instance method
      var cd = new CD();
      Y.Assert.isTrue(cd.does(T1));
      Y.ArrayAssert.containsItems([T1, T2], cd.does());
      Y.Assert.areSame(2+3, cd.does().length); // T1 + T2 + 3 anonymous traits
      // ... but should be overriden by instance methods
      var CE = Class.define({
        uses: T1,
        members:{does:function() { return 'a'; }}
      });
      var ce = new CE();
      Y.Assert.areSame('a', ce.does());
      // ... but not superclass methods
      var CF = Class.define({superclass: CE});
      var cf = new CF();
      Y.ArrayAssert.containsItems([T1], cf.does());

      // users should be able to override the does instance method
      var CG = Class.define({members: { does: function () { return 'a'; }}});
      var cg = new CG();
      Y.Assert.areSame('a', cg.does());
    },

    testSubtraits: function() {
      var T1 = Trait.define({});
      // a trait is not a subtrait of itself
      Y.Assert.isFalse(T1.subtraits(T1));
      // or some other random trait
      var T2 = Trait.define({});
      Y.Assert.isFalse(T1.subtraits(T2));
      Y.Assert.isFalse(T2.subtraits(T1));

			// immediate children are subtraits
      var T3 = Trait.define({ uses: [T1, T2] });
      Y.Assert.isTrue(T3.subtraits(T1));
      Y.Assert.isTrue(T3.subtraits(T2));

      var T4 = Trait.define({ uses: [T1, T3], methods: {f: function(){}} });
      // immediate children are subtraits
      Y.Assert.isTrue(T4.subtraits(T1));
			Y.Assert.isTrue(T4.subtraits(T3));
      // but not more distant relatives
      Y.Assert.isFalse(T4.subtraits(T2));

      // calling subtraits with no argument should return an array of all
      // subtraits
			Y.Assert.areSame(0, T1.subtraits().length);
      Y.ArrayAssert.containsItems([T1, T2], T3.subtraits());
      Y.Assert.areSame(2, T3.subtraits().length);
      Y.ArrayAssert.containsItems([T1, T3], T4.subtraits());
      Y.Assert.areSame(2, T4.subtraits().length);
    }
  }));


  /**************************************************************************
   * Run Tests
   *************************************************************************/

  var yconsole = new Y.Console({
    newestOnTop: false
  });
  yconsole.render();

  // Add all test suites to test runner here...
  Y.Test.Runner.add(class_suite);
  Y.Test.Runner.add(trait_suite);

  Y.Test.Runner.run();
});
